﻿// Inner Fire 游戏引擎库
// Model - 用于载入模型
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 用于模型载入。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-11

#include "skin_mesh.h"
#include "assimp_printer.h"
#include <klog.h>
#include <kits.h>

namespace ifire::game {

// TODO：这东西在这里，为了防止被析构之后，造成scene_的丢失。
// 但是之后需要在载入之后转化为自已的数据结构。不能常驻内存。
static Assimp::Importer importer;

const ModelMaterialInfo EMPTY_MATERIAL_INFO{};

SkinMesh::SkinMesh(Assets* manager, const XmlNode& node)
    : Resource(manager, node("Name"))
    , filename_(node("Path"))
    , scale_(node("Scale", 1.0f)) {
  materials_info_ = XmlNode_ReadItems<ModelMaterialInfo>(node, "Material");
  /*for (auto& e : materials_info_) {
    KLOG("Mesh: %s, Material: %s", e.name.c_str(), e.material.c_str());
  }*/
}

SkinMesh::~SkinMesh() {
  SAFE_DELETE_ARRAY(indices_);
  SAFE_DELETE_ARRAY(skinned_vertices_);
}

void SkinMesh::Load(ID3D12Device* device, ID3D12GraphicsCommandList* cmd_list) {
  // 创建MESH
  mesh_ = std::make_unique<dx::DxMesh>(0, Name());

  // 重置状态以用于统计
  ResetCurrentCount();

  /*KLOG("-- Load Model: %s --", filename_.c_str());*/

  auto flag = aiProcess_Triangulate | aiProcess_GenSmoothNormals |
              aiProcess_LimitBoneWeights | aiProcess_CalcTangentSpace |
              aiProcess_ConvertToLeftHanded;
  scene_ = importer.ReadFile(filename_.c_str(), flag);
  // AssimpPrinter printer(scene_, AssimpPrinterFlag::All);

  // 载入
  if (!scene_ || scene_->mFlags & AI_SCENE_FLAGS_INCOMPLETE ||
      !scene_->mRootNode) {
    KERROR("ERROR::ASSIMP:: %s", importer.GetErrorString());
    return;
  }

  KLOG("SkinMesh Load: %s", ikit::utf8_to_gbk(filename_).c_str());

  // 建立骨骼
  armature_ = std::make_unique<Armature>(scene_);

  // 打印材质
  PrintMaterial();

  // 计算顶点
  CountVerticesAndIndices();
  ReserveSpace();

  InitAllMeshes();

  // 创建Mesh的资源
  dx::DxMeshCreateDesc desc{};
  desc.pVertexData = skinned_vertices_;
  desc.pIndexData = indices_;
  desc.VertexSize = NumVertices;
  desc.VertexStrideSize = sizeof(dx::SkinnedVertex);
  desc.IndexSize = NumIndices;
  desc.IndexFormat = DXGI_FORMAT_R32_UINT;
  desc.IndexStrideSize = sizeof(std::uint32_t);
  mesh_->CreateResource(device, cmd_list, desc);
}

void VertexBoneData::AddBoneData(unsigned int BoneID, float Weight) {
  for (unsigned int i = 0; i < ARRAY_SIZE_IN_ELEMENTS(BoneIDs); i++) {
    if (Weights[i] == 0.0) {
      BoneIDs[i] = BoneID;
      Weights[i] = Weight;
      // printf("Adding bone %d weight %f at index %i\n", BoneID, Weight, i);
      return;
    }
  }

  // 理论上是不会执行到这里的。
  // 在载入的时候，使用aiProcess_LimitBoneWeights参数。可以使骨骼限定在指定范围内。
  // 在Unity中，以及各种资料的建议，最大4为好，所以本引擎也遵守这一规则。
  assert(0);
}

void SkinMesh::InitAllMeshes() {
  for (unsigned int i = 0; i < scene_->mNumMeshes; i++) {
    const aiMesh* paiMesh = scene_->mMeshes[i];
    InitSingleMesh(i, paiMesh);
  }
}

void SkinMesh::PrintMaterial() {
  for (unsigned int i = 0; i < scene_->mNumMaterials; i++) {
    const aiMaterial* item = scene_->mMaterials[i];
    KLOG("Material[%d]: %s", i, item->GetName().C_Str());
  }
}

void SkinMesh::InitSingleMesh(unsigned int MeshIndex, const aiMesh* paiMesh) {
  const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

  // 载入此Mesh的顶点
  for (unsigned int i = 0; i < paiMesh->mNumVertices; i++) {
    auto& v = skinned_vertices_[vertex_pointer_++];

    const aiVector3D& pPos = paiMesh->mVertices[i];
    // TODO：注意这里将X轴反转了。这样解决了载入FBX左右反转的问题。
    // 但是，这个东西的隐藏影响是未知的。
    v.Pos = XMFLOAT3(pPos.x * scale_, pPos.y * scale_, pPos.z * scale_);

    if (paiMesh->mNormals) {
      const aiVector3D& pNormal = paiMesh->mNormals[i];
      v.Normal = XMFLOAT3(pNormal.x, pNormal.y, pNormal.z);
    } else {
      aiVector3D Normal(0.0f, 1.0f, 0.0f);
      v.Normal = XMFLOAT3(Normal.x, Normal.y, Normal.z);
    }

    if (paiMesh->mTangents) {
      const aiVector3D& pTangent = paiMesh->mTangents[i];
      v.TangentU = XMFLOAT3(pTangent.x, pTangent.y, pTangent.z);
    } else {
      v.TangentU = XMFLOAT3(0.0f, 0.0f, 0.0f);
    }
    const aiVector3D& pTexCoord =
        paiMesh->HasTextureCoords(0) ? paiMesh->mTextureCoords[0][i] : Zero3D;
    v.TexC = XMFLOAT2(pTexCoord.x, pTexCoord.y);
  }

  // 载入本Mesh的骨骼。骨骼是以Mesh为标准载入的。
  LoadMeshBones(MeshIndex, paiMesh);

  // 载入此Mesh的索引
  for (unsigned int i = 0; i < paiMesh->mNumFaces; i++) {
    const aiFace& Face = paiMesh->mFaces[i];
    // TODO：注意这里更改了索引的顺序，这样就避免了X轴反转所带来的背面效果
    // 作用其实就是等于翻了一个面。
    indices_[index_pointer_++] = Face.mIndices[0];
    indices_[index_pointer_++] = Face.mIndices[1];
    indices_[index_pointer_++] = Face.mIndices[2];
  }
}

void SkinMesh::LoadMeshBones(unsigned int MeshIndex, const aiMesh* pMesh) {
  // KLOG("Start Load Mesh Bones(%d): %s", pMesh->mNumBones,
  // pMesh->mName.C_Str());

  for (unsigned int i = 0; i < pMesh->mNumBones; i++) {
    LoadSingleBone(MeshIndex, pMesh->mBones[i]);
  }
}

void SkinMesh::LoadSingleBone(unsigned int MeshIndex, const aiBone* pBone) {
  // KLOG("Bone: %s, Weights: %d", pBone->mName.C_Str(), pBone->mNumWeights);

  size_t bone_id = armature_->GetBoneId(ikit::get_string_uid(pBone->mName.C_Str()));

  //// 检查这个BoneID是否存在。如果是新加入的，需要给它在Info中分配空间
  //if (bone_id == armature_->bones_.size()) {
  //  BoneInfo bi(pBone->mOffsetMatrix);
  //  armature_->bones_.push_back(bi);
  //}

  // 将骨骼的权重写入到顶点的骨骼里面。
  // TODO:应该在这里检查4个骨骼的Limit?
  for (unsigned int i = 0; i < pBone->mNumWeights; i++) {
    const aiVertexWeight& vw = pBone->mWeights[i];
    unsigned int global_vertex_id =
        mesh_->Submeshs()[MeshIndex].vertex_start + vw.mVertexId;
    AddBoneData(global_vertex_id, static_cast<unsigned int>(bone_id), vw.mWeight);
    // skinned_vertices_[global_vertex_id].AddBoneData(bone_id, vw.mWeight);
  }
}

void SkinMesh::AddBoneData(
    unsigned int vertex_index, unsigned int bone_id, float weight) {
  auto& v = skinned_vertices_[vertex_index];
  // 依次设置x,y,z值
  if (v.BoneWeights.x == 0.0f) {
    v.BoneIndices[0] = bone_id;
    v.BoneWeights.x = weight;
    return;
  }
  if (v.BoneWeights.y == 0.0f) {
    v.BoneIndices[1] = bone_id;
    v.BoneWeights.y = weight;
    return;
  }
  if (v.BoneWeights.z == 0.0f) {
    v.BoneIndices[2] = bone_id;
    v.BoneWeights.z = weight;
    return;
  }
}

void SkinMesh::LoadBones(const aiScene* scene, ID3D12Device* device,
    ID3D12GraphicsCommandList* cmd_list) {
  auto root = scene->mRootNode;
  for (unsigned int i = 0; i < root->mNumMeshes; i++) {
    aiMesh* mesh = scene->mMeshes[root->mMeshes[i]];
    KLOG("Mesh: %s, Bouns:%d", mesh->mName.C_Str(), mesh->mNumBones);
  }
  for (unsigned int i = 0; i < root->mNumChildren; i++) {
    auto child = root->mChildren[i];
    for (unsigned int j = 0; j < child->mNumMeshes; j++) {
      aiMesh* mesh = scene->mMeshes[child->mMeshes[j]];
      KLOG("Child Mesh: %s, Bouns:%d", mesh->mName.C_Str(), mesh->mNumBones);
    }
  }

  KLOG("mNumSkeletons: %d", scene->mNumSkeletons);
  for (unsigned int i = 0; i < scene->mNumSkeletons; i++) {
    auto& skeleton = scene->mSkeletons[i];
    // KLOG("Skeleton: %s, mNumBouns: %d", skeleton->mName.C_Str(),
    //     skeleton->mNumBones);
  }
}

const ModelMaterialInfo& SkinMesh::GetMaterialInfo(
    const std::string_view& name) const {
  for (auto& e : materials_info_) {
    if (e.name == name) {
      return e;
    }
  }
  return EMPTY_MATERIAL_INFO;
}

const ModelMaterialInfo& SkinMesh::GetMaterialInfo(int index) const {
  if (index >= 0 && index < materials_info_.size()) {
    return materials_info_[index];
  }
  return EMPTY_MATERIAL_INFO;
}

void SkinMesh::ProcessNode(aiNode* node, const aiScene* scene, int layer) {

  KLOG("Node:%s, Meshes:%d", node->mName.C_Str(), node->mNumMeshes);

  for (unsigned int i = 0; i < node->mNumMeshes; i++) {
    aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
    AddSubMesh(mesh, layer);
  }
  // 递归处理子节点
  for (unsigned int i = 0; i < node->mNumChildren; i++) {
    ProcessNode(node->mChildren[i], scene, layer + 1);
  }
}

void SkinMesh::AddSubMesh(aiMesh* mesh, int layer) {
  // 因为都是三角面，所以面x3即可得到索引数
  int vertex_count = mesh->mNumVertices;
  int index_count = mesh->mNumFaces * 3;

  // KLOG("%s SUBMESH(%s) MaterialIndex(%d) Index(%d) Vertex(%d) Bouns(%d)",
  //     get_layer_star(layer), mesh->mName.C_Str(), mesh->mMaterialIndex,
  //     index_count, vertex_count, mesh->mNumBones);

  dx::Vertex vertex{};
  for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
    auto& vertex = vertices_[vertex_pointer_++];
    vertex.pos = XMFLOAT3(mesh->mVertices[i].x * scale_,
        mesh->mVertices[i].y * scale_, mesh->mVertices[i].z * scale_);
    vertex.normal =
        XMFLOAT3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
    // 假设纹理坐标和切线存在
    if (mesh->mTextureCoords[0]) {
      vertex.texcoord =
          XMFLOAT2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
    } else {
      vertex.texcoord = XMFLOAT2(0.0f, 0.0f);
    }
    if (mesh->mTangents) {
      vertex.tangent = XMFLOAT3(
          mesh->mTangents[i].x, mesh->mTangents[i].y, mesh->mTangents[i].z);
    } else {
      vertex.tangent = XMFLOAT3(0.0f, 0.0f, 0.0f);
    }
    // KLOG("Vertex: %f,%f,%f", vertex.pos.x, vertex.pos.y, vertex.pos.z);
    // vertices_.push_back(vertex);
  }

  for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
    const aiFace& face = mesh->mFaces[i];
    // KPRINT("\nFace(%d): ", face.mNumIndices);
    for (unsigned int j = 0; j < face.mNumIndices; j++) {
      indices_[index_pointer_++] = face.mIndices[j];
      // KPRINT("%d ", face.mIndices[j]);
    }
  }

  // KLOG("\n\n-- SUBMESH END --\n");

  // 得到当前项目的SubMesh
  dx::DxSubmesh& sub = mesh_->AddSubmesh(mesh->mName.C_Str(),
      mesh->mMaterialIndex, index_count, current_index_, current_vertex_);

  // 计算包围盒子
  auto start_pointer = vertices_ + current_vertex_;
  BoundingBox::CreateFromPoints(
      sub.bounding_box, vertex_count, &start_pointer->pos, sizeof(dx::Vertex));
  // 步进下一个项目
  current_index_ += index_count;
  current_vertex_ += mesh->mNumVertices;
}

void SkinMesh::ResetCurrentCount() {
  current_index_ = 0;
  current_vertex_ = 0;
}

///////////////////////////////////////////////////////////////////////////////
// SkinMesh相关
///////////////////////////////////////////////////////////////////////////////

void SkinMesh::ReserveSpace() {
  indices_ = new std::uint32_t[NumIndices];
  index_pointer_ = 0;

  skinned_vertices_ = new dx::SkinnedVertex[NumVertices];
  vertex_pointer_ = 0;
}

void SkinMesh::CountVerticesAndIndices() {
  NumVertices = 0;
  NumIndices = 0;
  for (unsigned int i = 0; i < scene_->mNumMeshes; i++) {
    // 设置SubMesh
    auto& mesh = scene_->mMeshes[i];
    auto& sub_mesh =
        mesh_->AddSubmesh(mesh->mName.C_Str(), mesh->mMaterialIndex,
            static_cast<UINT>(mesh->mNumFaces * 3), NumIndices, NumVertices);

    NumVertices += mesh->mNumVertices;
    NumIndices += sub_mesh.index_count;
  }
}

#pragma region 打印项目相关

#pragma endregion

} // namespace ifire::game